#include "../common/config.h"
#include <boost/format.hpp>
#include <boost/lexical_cast.hpp>
#include <boost/algorithm/string.hpp>
#ifdef USE_LIBXML
#include <libxml/parser.h>
#include <libxml/tree.h>
#else
#include <fstream>
#include <rapidxml.hpp>
#endif

#include "../common/logmanager.h"
#include "httpxmlcfg.h"

using namespace std;
using namespace log4cxx;
namespace ba = boost::algorithm;

#ifdef USE_LIBXML

const char* xmlNodeGetName(xmlNodePtr node)
{
	return node->name;
}

// Get next element node
xmlNodePtr xmlNodeGetNext(xmlNodePtr node, const char* name)
{
	xmlNodePtr ret = NULL;

	if(node != NULL)
	{
		ret = node->next;
		while(ret != NULL)
		{
			if(ret->type == XML_ELEMENT_NODE)
			{
				if(name == NULL)
					break;
				
				if(!strcmp((const char*)ret->name, name))
				{
					break;
				}
			}
			ret = ret->next;
		}
	}

	return ret;
}

// Get first child element node
xmlNodePtr xmlNodeGetFirstChild(xmlNodePtr node, const char* name)
{
	xmlNodePtr ret = NULL;

	if(node != NULL)
	{
		ret = node->children;
		while(ret != NULL)
		{
			if(ret->type == XML_ELEMENT_NODE)
			{
				if(name == NULL)
					break;
				
				if(!strcmp((const char*)ret->name, name))
				{
					break;
				}
			}
			ret = ret->next;
		}
	}

	return ret;
}

bool xmlNodeGetPropAsBool(xmlNodePtr node, const char* name, bool default_value)
{
	if(!node)
		return default_value;

	xmlChar* value;
	if((value = xmlGetProp(node, BAD_CAST name)) == NULL)
		return default_value;

	bool ret;
	ret = ba::iequals(string((char*)value), "true");
	xmlFree(value);

	return ret;
}

string xmlNodeGetPropAsString(xmlNodePtr node, const char* name, const char * default_value)
{
	if(!node)
		return string(default_value);

	xmlChar* value;
	if((value = xmlGetProp(node, BAD_CAST name)) == NULL)
		return string(default_value);

	string ret = (const char*)value;
	xmlFree(value);

	return ret;
}

bool xmlNodeGetFirstChildContent(xmlNodePtr node, const char* name, string & value)
{
	xmlNodePtr child;

	child = xmlNodeGetFirstChild(node, name);
	if(child == NULL)
		return false;

	xmlChar *v = xmlNodeGetContent(child);
	if(v == NULL)
		return false;

	value = string((const char*)v);
	xmlFree(v);
	return true;
}
#else

using namespace rapidxml;

const char* xmlNodeGetName(xml_node<char>* node)
{
	return node->name();
}

xml_node<char>* xmlNodeGetNext(xml_node<char>* node, const char* name)
{
	xml_node<char>* ret = NULL;

	if(node != NULL)
	{
		ret = node->next_sibling();
		while(ret != NULL)
		{
			if(ret->type() == node_element)
			{
				if(name == NULL)
					break;
				
				if(!strcmp(ret->name(), name))
				{
					break;
				}
			}
			ret = ret->next_sibling();
		}
	}

	return ret;
}

xml_node<char>* xmlNodeGetFirstChild(xml_node<char>* node, const char* name)
{
	xml_node<char> *ret = NULL;

	if(node != NULL)
	{
		ret = node->first_node();
		while(ret != NULL)
		{
			if(ret->type() == node_element)
			{
				if(name == NULL)
					break;
				
				if(!strcmp(ret->name(), name))
				{
					break;
				}
			}
			ret = ret->next_sibling();
		}
	}

	return ret;
}

bool xmlNodeGetPropAsBool(xml_node<char>* node, const char* name, bool default_value)
{
	if(!node)
		return default_value;

	char* value = NULL;
	for (xml_attribute<> *attr = node->first_attribute();
					attr; attr = attr->next_attribute())
	{
		if(!strcmp(name, attr->name()))
		{
			value = attr->value();
			break;
		}
	}

	if(!value)
		return default_value;

	bool ret;
	ret = ba::iequals(string((char*)value), "true");

	return ret;
}

string xmlNodeGetPropAsString(xml_node<char>* node, const char* name, const char * default_value)
{
	if(!node)
		return string(default_value);

	char* value = NULL;
	for (xml_attribute<> *attr = node->first_attribute();
					attr; attr = attr->next_attribute())
	{
		if(!strcmp(name, attr->name()))
		{
			value = attr->value();
			break;
		}
	}

	if(!value)
		return string(default_value);

	string ret = (const char*)value;
	return ret;
}

bool xmlNodeGetFirstChildContent(xml_node<char>* node, const char* name, string & value)
{
	xml_node<char>* child;

	child = xmlNodeGetFirstChild(node, name);
	if(child == NULL)
		return false;

	char *v = child->value();
	if(v == NULL)
		return false;

	value = string((const char*)v);
	return true;
}

#endif

HTTPXMLCfg::HTTPXMLCfg()
{
}

HTTPXMLCfg::HTTPXMLCfg(const HTTPXMLCfg & config) : HTTPConfig(config)
{
}

HTTPXMLCfg & HTTPXMLCfg::operator=(const HTTPXMLCfg & config)
{
	if(this != &config)
	{
		HTTPConfig::operator=(config);
	}

	return *this;
}

int HTTPXMLCfg::load(const string & filename)
{
#ifdef USE_LIBXML
	xmlDocPtr xmldoc;
	xmlNodePtr xmlroot;
	xmlNodePtr xmltmp;

	if((xmldoc = xmlReadFile(filename.c_str(), NULL, 0)) == NULL)
	{
		LogManager::initlog("cannot load config file " + filename);
		return ERROR_FILE;
	}

	if((xmlroot = xmlDocGetRootElement(xmldoc)) == NULL)
	{
		LogManager::initlog("cannot parse xml root element");
		return ERROR_PARSE;
	}
#else
	ifstream xmlfile;
	filebuf *xmlbuf;
	long size;
	char * xmldata;

	xml_document<> xmldoc;
	xml_node<> *xmlroot;
	xml_node<> *xmltmp;

	xmlfile.open(filename.c_str(), ios::in | ios::binary);
	if(xmlfile.good() == false)
	{
		LogManager::initlog("cannot load config file " + filename);
		return ERROR_FILE;
	}
	
	xmlbuf = xmlfile.rdbuf();
	size = xmlbuf->pubseekoff(0, ios::end, ios::in);
	xmlbuf->pubseekpos(0, ios::in);

	xmldata = new char[size+1];
	xmlbuf->sgetn(xmldata, size);
	xmldata[size] = 0x0;
	xmlfile.close();

	try
	{
		xmldoc.parse<0>(xmldata);
	}
	catch(parse_error & e)
	{
		LogManager::initlog(string(e.what()) + " - cannot parse xml at " + string(e.where<char>()));
		return ERROR_PARSE;
	}
	xmlroot = xmldoc.first_node();
#endif

	
	string tmpvalue;

	if(xmlNodeGetFirstChildContent(xmlroot, "listen", tmpvalue) == true)
	{
		boost::system::error_code ec;
		m_listening_address = boost::asio::ip::address_v4::from_string(tmpvalue, ec);
		if(ec)
		{
			LogManager::initlog("cannot parse the listening address - " + tmpvalue);
		}
	}

	if(xmlNodeGetFirstChildContent(xmlroot, "port", tmpvalue) == true)
	{
		try
		{
			m_listening_port = boost::lexical_cast<unsigned short>(tmpvalue);
		}
		catch(...)
		{
			LogManager::initlog("cannot parse the listening port - " + tmpvalue);
		}
	}

	if(xmlNodeGetFirstChildContent(xmlroot, "threads", tmpvalue) == true)
	{
		try
		{
			m_threads = boost::lexical_cast<unsigned short>(tmpvalue);
		}
		catch(...)
		{
			LogManager::initlog("cannot parse the number of threads - " + tmpvalue);
		}
	}

	if(xmlNodeGetFirstChildContent(xmlroot, "username", tmpvalue) == true)
	{
		m_proc_username = tmpvalue;
	}

	if(xmlNodeGetFirstChildContent(xmlroot, "groupname", tmpvalue) == true)
	{
		m_proc_groupname = tmpvalue;
	}
	
	if((xmltmp = xmlNodeGetFirstChild(xmlroot, "auth")) == NULL)
	{
		m_isauthenabled = false;
	}
	else
	{
		m_isauthenabled = xmlNodeGetPropAsBool(xmltmp, "enabled", true);

		xmltmp = xmlNodeGetFirstChild(xmltmp, NULL);
		while(xmltmp != NULL)
		{
			if(!strcmp(xmlNodeGetName(xmltmp), "user"))
			{
				HTTPUser h;

				if(xmlNodeGetFirstChildContent(xmltmp, "name", tmpvalue) == false)
				{
					LogManager::initlog("cannot read username of auth section");
					xmltmp = xmlNodeGetNext(xmltmp, NULL);
					continue;
				}
				else
				{
					h.setUsername(tmpvalue);
				}
								
				if(xmlNodeGetFirstChildContent(xmltmp, "pass", tmpvalue) == false)
				{
					LogManager::initlog("cannot read password of auth section");
					xmltmp = xmlNodeGetNext(xmltmp, NULL);
					continue;
				}
				else
				{
					h.setPassword(tmpvalue);
				}

				m_singleusers.push_back(h);
			}
			else if(!strcmp(xmlNodeGetName(xmltmp), "userfile"))
			{
				if(xmlNodeGetFirstChildContent(xmltmp, "filename", tmpvalue) == false)
				{
					LogManager::initlog("cannot read userdb filename of auth section");
					xmltmp = xmlNodeGetNext(xmltmp, NULL);
					continue;
				}
				else
				{
					m_userdbs.push_back(tmpvalue);
				}
			}
			else
			{
				LogManager::initlog(boost::str(boost::format("not-supported <%s> in <auth> section") %
																xmlNodeGetName(xmltmp)));
			}
			
			xmltmp = xmlNodeGetNext(xmltmp, NULL);
		}
	}

	// parse user-defined upstream proxies
	if((xmltmp = xmlNodeGetFirstChild(xmlroot, "upproxy")) != NULL)
	{
		xmltmp = xmlNodeGetFirstChild(xmltmp, "proxy");
		while(xmltmp != NULL)
		{
			UpstreamProxy proxy;

			if(xmlNodeGetFirstChildContent(xmltmp, "name", tmpvalue) == false)
			{
				LogManager::initlog("cannot read proxy name of upproxy section");
				xmltmp = xmlNodeGetNext(xmltmp, "proxy");
				continue;
			}
			else
			{
				proxy.setName(tmpvalue);
			}
			
			if(xmlNodeGetFirstChildContent(xmltmp, "host", tmpvalue) == false)
			{
				LogManager::initlog("cannot read proxy host of upproxy section");
				xmltmp = xmlNodeGetNext(xmltmp, "proxy");
				continue;
			}
			else
			{
				proxy.setHost(tmpvalue);
			}
			
			if(xmlNodeGetFirstChildContent(xmltmp, "port", tmpvalue) == false)
			{
				LogManager::initlog("cannot read proxy port of upproxy section");
				xmltmp = xmlNodeGetNext(xmltmp, "proxy");
				continue;
			}
			else
			{
				unsigned short port;
				try
				{
					port = boost::lexical_cast<unsigned short>(tmpvalue);
				}
				catch(...)
				{
					LogManager::initlog("cannot parse proxy port of upproxy section");
					xmltmp = xmlNodeGetNext(xmltmp, "proxy");
					continue;
				}
				proxy.setPort(port);
			}

			m_upstream_proxies.push_back(proxy);
			xmltmp = xmlNodeGetNext(xmltmp, "proxy");
		}
	}

	// parse proxy redirection rules
	if((xmltmp = xmlNodeGetFirstChild(xmlroot, "upstream")) == NULL)
	{
		m_isupstreamenabled = false;
	}
	else
	{
		m_isupstreamenabled = xmlNodeGetPropAsBool(xmltmp, "enabled", true);

		xmltmp = xmlNodeGetFirstChild(xmltmp, "rule");
		while(xmltmp != NULL)
		{
			UpstreamRule ur;
			
			if(xmlNodeGetFirstChildContent(xmltmp, "src", tmpvalue) == true)
			{
				ur.setSource(tmpvalue);
			}

			if(xmlNodeGetFirstChildContent(xmltmp, "dst", tmpvalue) == true)
			{
				ur.setDestination(tmpvalue);
			}

			if(xmlNodeGetFirstChildContent(xmltmp, "proxy", tmpvalue) == true)
			{
				ur.setProxyName(tmpvalue);
			}
			else
			{
				LogManager::initlog("rule proxy is empty");
				xmltmp = xmlNodeGetNext(xmltmp, "rule");
				continue;
			}

			m_upstream_rules.push_back(ur);
			xmltmp = xmlNodeGetNext(xmltmp, "rule");
		}
	}

	// parse access rules
	if((xmltmp = xmlNodeGetFirstChild(xmlroot, "acl")) == NULL)
	{
		m_isaccess_rule_enabled = false;
	}
	else
	{
		m_isaccess_rule_enabled = xmlNodeGetPropAsBool(xmltmp, "enabled", true);
		tmpvalue = xmlNodeGetPropAsString(xmltmp, "default", "allow");
		if(ba::iequals(tmpvalue, "allow"))
			m_access_default_action = AccessRule::ALLOW;
		else
			m_access_default_action = AccessRule::DENY;

		xmltmp = xmlNodeGetFirstChild(xmltmp, "rule");
		while(xmltmp != NULL)
		{
			AccessRule ar;

			if(xmlNodeGetFirstChildContent(xmltmp, "src", tmpvalue) == true)
			{
				ar.setSource(tmpvalue);
			}

			if(xmlNodeGetFirstChildContent(xmltmp, "dst", tmpvalue) == true)
			{
				ar.setDestination(tmpvalue);
			}

			if(xmlNodeGetFirstChildContent(xmltmp, "action", tmpvalue) == true)
			{
				if(ba::iequals(tmpvalue, "allow"))
					ar.setAction(AccessRule::ALLOW);
				else
					ar.setAction(AccessRule::DENY);
			}

			m_access_rules.push_back(ar);
			xmltmp = xmlNodeGetNext(xmltmp, "rule");
		}
	}

	// parse log settings
	if((xmltmp = xmlNodeGetFirstChild(xmlroot, "log")) == NULL)
	{
		m_log_enabled = false;
	}
	else
	{
		m_log_enabled = xmlNodeGetPropAsBool(xmltmp, "enabled", true);

		xmltmp = xmlNodeGetFirstChild(xmltmp, "logger");
		while(xmltmp != NULL)
		{
			LogConfig lc;
			
			if(xmlNodeGetFirstChildContent(xmltmp, "name", tmpvalue) == false)
			{
				xmltmp = xmlNodeGetNext(xmltmp, "logger");
				continue;
			}
			else
			{
				lc.m_name = tmpvalue;
			}

			if(xmlNodeGetFirstChildContent(xmltmp, "file", tmpvalue) == false)
			{
				xmltmp = xmlNodeGetNext(xmltmp, "logger");
				continue;
			}
			else
			{
				lc.m_filename = tmpvalue;
			}

			// optional
			if(xmlNodeGetFirstChildContent(xmltmp, "level", tmpvalue) == true)
			{
				lc.m_level = tmpvalue;
			}

			// optional
			if(xmlNodeGetFirstChildContent(xmltmp, "filenum", tmpvalue) == true)
			{
				int num;
				try
				{
					num = boost::lexical_cast<int>(tmpvalue);
				}
				catch(...)
				{
					LogManager::initlog("cannot parse the content of <filenum> - " + tmpvalue);
					xmltmp = xmlNodeGetNext(xmltmp, "logger");
					continue;
				}
				lc.m_backupindex = num;
			}

			if(lc.m_name == "httpproxy")
			{
				m_proxylog = lc;
			}
			else if(lc.m_name == "access")
			{
				m_accesslog = lc;
			}
			else if(lc.m_name == "debugger")
			{
				m_debugger = lc;
			}
			else
			{
				LogManager::initlog("unknown logger name - " + lc.m_name);
			}
			
			xmltmp = xmlNodeGetNext(xmltmp, "logger");
		}
	}

	// parse web gui settings
	if((xmltmp = xmlNodeGetFirstChild(xmlroot, "webgui")) == NULL)
	{
		m_web_enabled = false;
	}
	else
	{
		m_web_enabled = xmlNodeGetPropAsBool(xmltmp, "enabled", true);

		if(xmlNodeGetFirstChildContent(xmltmp, "listen", tmpvalue) == true)
		{
			boost::system::error_code ec;
			m_web_listenaddr = boost::asio::ip::address_v4::from_string(tmpvalue, ec);
			if(ec)
			{
				LogManager::initlog("cannot parse the web-gui listening address - " + tmpvalue);
			}
		}

		if(xmlNodeGetFirstChildContent(xmltmp, "port", tmpvalue) == true)
		{
			try
			{
				m_web_listenport = boost::lexical_cast<unsigned short>(tmpvalue);
			}
			catch(...)
			{
				LogManager::initlog("cannot parse the web-gui listening port - " + tmpvalue);
			}
		}

		if(xmlNodeGetFirstChildContent(xmltmp, "docroot", tmpvalue) == true)
		{
			m_web_docroot = tmpvalue;
		}
	}

#ifdef USE_LIBXML
	if(xmldoc != NULL)
		xmlFreeDoc(xmldoc);
#else
	if(xmldata != NULL)
		delete xmldata;
#endif
	
	return SUCCESS;
}

int HTTPXMLCfg::save(const string & filename)
{
	return SUCCESS;
}

